////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_H__
#define __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_H__

#ifndef __WN_MATH_INTERNAL_ELEMENTS_H__
    #error "Internal/x86/SSE/WNElements.h should never be included directly."
#elif !defined _WN_X86
    #error "Internal/x86/SSE/WNElements.h has been included on a non x86 platform. Please rectify this."
#elif !defined __WN_MATH_ENABLE_EXTENSIONS
    #error "Internal/x86/SSE/WNElements.h has been included when extensions are disabled. Please rectify this."
#elif !defined __WN_MATH_SSE_ALLOWED
    #error "Internal/x86/SSE/WNElements.h has been included when SSE is not allowed. Please rectify this."
#endif

#include <xmmintrin.h>

namespace WNMath {
    namespace __WNInternal {
        #ifdef __WN_MATH_SSE_AVAILABLE
            #ifdef __WN_MATH_AVX_AVAILABLE
                template <WN_UINT32 Dimension>
                struct WN_ALIGN(16) __WNElements<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1 && Dimension <= 4)>::Value> {
                    WN_STATIC_ASSERT_DESC(Dimension > 0, "Must have an element base of at least 1");

                    union {
                        WN_FLOAT32 mValues[Dimension];
                        __m128 mXMMValues[(Dimension + 3) >> 2];
                    };
                };
            #else
                template <WN_UINT32 Dimension>
                struct WN_ALIGN(16) __WNElements<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
                    WN_STATIC_ASSERT_DESC(Dimension > 0, "Must have an element base of at least 1");

                    union {
                        WN_FLOAT32 mValues[Dimension];
                        __m128 mXMMValues[(Dimension + 3) >> 2];
                    };
                };
            #endif

            // N Element, WN_FLOAT32

            template <WN_UINT32 Dimension>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const WN_FLOAT32& _number);

            template <WN_UINT32 Dimension>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const WN_FLOAT32* _numbers);

            // 4 Element, WN_FLOAT32

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 4>& _element1, const WN_FLOAT32& _number);

            // 3 Element, WN_FLOAT32

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 3>& _element1, const WN_FLOAT32& _number);

            // 2 Element, WN_FLOAT32

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 2>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 2>& _element1, const WN_FLOAT32& _number);
        #else
            #ifdef __WN_MATH_AVX_ALLOWED
                template <WN_UINT32 Dimension>
                struct WN_ALIGN(16) __WNElements<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1 && Dimension <= 4)>::Value> {
                    WN_STATIC_ASSERT_DESC(Dimension > 0, "Must have an element base of at least 1");

                    WN_FLOAT32 mValues[Dimension];
                };

                template <WN_UINT32 Dimension>
                struct WN_ALIGN(32) __WNElements<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 4)>::Value> {
                    WN_STATIC_ASSERT_DESC(Dimension > 0, "Must have an element base of at least 1");

                    WN_FLOAT32 mValues[Dimension];
                };
            #else
                template <WN_UINT32 Dimension>
                struct WN_ALIGN(16) __WNElements<WN_FLOAT32, Dimension, typename WNCore::WNEnableWhen<(Dimension > 1)>::Value> {
                    WN_STATIC_ASSERT_DESC(Dimension > 0, "Must have an element base of at least 1");

                    WN_FLOAT32 mValues[Dimension];
                };
            #endif
        #endif

        // N Element, WN_FLOAT32

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplyAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplyAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2);

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number);

        // 4 Element, WN_FLOAT32

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number);

        // 3 Element, WN_FLOAT32

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 3>& _element1, const __WNElements<WN_FLOAT32, 3>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 3>& _element, const WN_FLOAT32& _number);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 3>& _element1, const __WNElements<WN_FLOAT32, 3>& _element2);

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 3>& _element, const WN_FLOAT32& _number);
    }
}

#include "WNMath/inc/Internal/x86/SSE/WNElements.inl"

#endif // __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_H__